AYA 初心者入門 †此篇教學適用於完全不懂程式語言的新手,已有程式基礎的也可以當作課外讀物(?)參考一下。 開始製作之前 †由於觀看此篇的開發者們應該都沒什麼基礎,所以我們就從 AYA 的中文人格範本『紺野芽芽美』開始入手。請先下載並安裝核心程式 SSP ,然後將紺野芽芽美的 nar 檔拖曳至 SSP 來安裝。安裝完成後請根據你放置 SSP 的路徑,找到放置紺野芽芽美人格辭書的地方。 假設你的 SSP 是放在 C:\SSP 底下的話,那麼紺野芽芽美的人格路徑即為 C:\SSP\ghost\konnoyayame\ghost\master 接下來你應該會看到這樣子的資料夾跟人格樣貌,以下教學中出現的路徑都將以此資料夾為根目錄。 那麼,我們可以開始對她進行適度的研究(?)了。難度有從 LV 1到 LV 5,請適當的挑選自己程度以內的教學來看吧。 常識類 †『On...』事件的自動跳轉 †核心程式(SSP等)在觸發使用者點選選項、輸入文字等事件的時候,會預先判定第一個引數的開頭是否為『On...』,不是的話會正常發生系統內建 的事件(如 OnChoiceSelect 及 OnUserInput ),並將此第一引數視為 reference0 ;但如果第一引數為『On...』開頭的話,那麼核心程式就不會觸發預設事件,而會自動跳轉到這個名為『On...』的函式,然後將第二引數視為 reference0 。 \q[選項一, Event, reference1, reference2] \q[選項二, OnEvent, reference0, reference1] 基於程式流程上的便利,個人傾向於使用『On...』作為函式的開頭,本文以下也有一部份使用此法,特此註明。 對話類 †屬於自己的對話 (LV 1) †初心者們在製作第一個人格時,想必都想先讓她講出自己所希望的話吧。在紺野芽芽美的設定中,關於AI對話的部份是放在 \dic\aya_aitalk.dic 裡面。這個檔案可以直接用記事本開啟,或是EmEditor這類文件編輯工具。當你打開這個文件檔後,應該可以看到這樣的畫面: 接下來只要將遵照著 SakuraScript 語法寫好的對話放入上圖中 RandomTalk 的上下大括號『{ }』之內的話,你的對話就能出現在芽芽美的隨機對話之中了。對話前後要用雙引號『"』括起來,才算一段對話,所以請不要忘了雙引號的存在,否則可能會導致編譯錯誤,打不開人格喔。 對話事件後的反應 (LV 1) †說到事件這種東西,很多人第一個念頭就是親密度(?)的上升吧?所以這裡就是在教你如何簡單快速的對每個對話設定親密度的影響。 在AYA的系統中,有一種後置EVAL方法的設定,會自動分析輸出字串的尾端,如果包含『:eval=』這樣的字串的話,便會對這個字串後面的內容進行一次EVAL演算。其例子如下: "\0總覺得剛剛心裡撲通了一下。\e:eval=(intimacy+=2)" 這樣一來,當人格每次講出了這段對話後,程式會將『intimacy+=2』視為一段程式敘述,然後叫做 intimacy*1 的變數就會被加二了。 事實上,這樣的方式在 AYA 中可以有很多種寫法,以下是等價的例子: "\0總覺得剛剛心裡撲通了一下。\e:eval=(intimacy+=2)" "\0總覺得剛剛心裡撲通了一下。\e" + EVAL("intimacy+=2") "\0總覺得剛剛心裡撲通了一下。\e%(intimacy+=2)" 要注意的是上述的方法中都是有返值的,如果把式子寫在『\e』之前的話,你會發現對話中多了一個數字,為 intimacy 的值。所以一般建議是將改變的部份寫在最尾端,這樣以後要查找或更改其內容時也比較省事。 另外,當你新設了一個變數的時候,請記得式子前面先宣告變數(像是 intimacy = 0 這樣),再做加減。所有已儲存的變數都可在 yaya_variable.cfg 這個檔案中找到,可藉由觀察這個檔案的變化來確認你所執行的程式是否有產生反應。參見 程式類 - 觀察變數的變化 (LV 2) 連鎖對話 (LV 2) †基本上就是在想要產生連鎖對話的對話後面加上『:chain=????』這樣格式的字串,然後再寫一個名字為????的函式,然後用 『{{CHAIN』、『}}CHAIN』包圍起來即可。對話會一直進行到所有對話都進行過一次、或是對話出現『:chain=end』這樣的字串時,就會 自動結束。 如果在連鎖對話的函式中再追加大括號的話,其意義為隨機抽取大括號中任一個對話。另外,連鎖函式與普通函式一樣可以使用變數或 if 判定式。而連鎖之內也可以加入新的連鎖,因此可以寫出相當複雜的連鎖程式。以下簡單的例子所示。 chain1 {{CHAIN "1" "2" { "3" "4:chain=end" "5:chain=chain2" } "6" }}CHAIN chain2 {{CHAIN "7" "8" }}CHAIN 此連鎖函式 chain1 會隨機產生下列3種型式的連鎖對話。 1. "1" → "2" → "3" → "6" → 終了 2. "1" → "2" → "4" → 終了 3. "1" → "2" → "5" → "7" → "8" → 終了 同型對話 - 候補篇 (LV1) †在 AYA 中,有一種比較特別的演算子『--』,稱為輸出確定子,可以將『--』前後的輸出候補各取一值作加算,詳細方法請參考AYA Version 5 說明文書。 這個方法的好處是對於輸出的情況一目瞭然,寫作上也方便很多。不過,在使用此種寫法時,建議不要與其他的程式碼交雜在一起,最好是把要輸出的字串統一整合在一個子階層內,以免產生混亂。 同型對話 - 函式篇 (LV2) †在『屬於自己的對話 (LV 1)』的圖示中,是否有這樣的一句對話呢? "\1\s[10]我們的中文化好像沒有問過任何人耶,真的可以嗎?\w6" + ANY("\0\s[0]噓,\w5那種危險的話,\w5不要亂講。\e", "\0\s[0]算是公有財產,\w5應該沒關係吧。\e", "\0\s[0]多學一個語言,\w5也不錯吧…\w5…\w5大概。\e") 這句的對話使用了 ANY 這個AYA內建函式,這個函式會讓對話內容在實際的輸出中隨機選擇函式的任一個參數作輸出,因而產生多種不同的結果。由於上面的對話有些複雜(哪方面?XD),我們來造一個比較簡單的例子,例如: "\0請問1+1等於多少?" + ANY("\1答案是2。\0答對了~。","\1答案是10。\0……你來亂的嗎?","\1答案是10。\0……二進位是吧。") 上述句子會有三種可能的結果,分別是: \0請問1+1等於多少?\1答案是2。\0答對了~。 \0請問1+1等於多少?\1答案是10。\0……你來亂的嗎? \0請問1+1等於多少?\1答案是10。\0……二進位是吧。 這樣一來同一個主題便可以有多種不同的結果,對話的內容也更加的多元化。順道一提,同型對話在某程度上與連鎖對話相當類似,唯一的不同點就在於它是一次完成對話。請善用這個特點,將對話做得更加有趣吧。 程式類 †變數名稱 (LV 1) †在寫程式的過程中,我們經常定義一些變數來存放我們的資料。在C語言中,變數名稱通常都是一堆的英文,不過由於AYA本身完全支援 UTF-8 的關係,在AYA中,我們即使將自定義的函式名或變數名指定為中文也是沒有問題的。這樣的好處是能讓我們快速理解變數的作用與運作方式,缺點則是變數的名 稱可能會跟字串內容混雜在一起,以及檔案變大等問題。 而為了將字串與變數名稱區別開來,建議在每個中文變數或函式前面加個全形字如『$』、『@』等,讓自己能一看就分辨出來。 當然,對習慣於正規程式寫法的人來說,使用英文可能遠比使用中文來得易於理解,所以這樣的寫法並沒有絕對性,僅作為一個參考,至於使不使用就看各人的習慣與偏好了。 觀察變數的變化 (LV 1) †由於 AYA 並沒有像 VC++ 那樣的 Debug 系統,當你要確認你寫的程式在 AYA 中是否良好的運作時,唯一的方法便是觀察變數在程式執行後的反應。 AYA 的所有變數預設儲存於 yaya_variable.cfg 檔案中,這個檔案一樣只需要記事本程式即可開啟。不過,在 AYA 的預設辭書之中, yaya_variable.cfg 是每30分鐘才儲存一次,我們當然不可能等那麼久,所以要對系統辭書 aya_shiori3.dic 作點修正,首先請找到這邊: SAVEVAR 便是執行儲存變數動作的函式, SHIORI3FW.Svvercount 是系統辭書用來判定是否到了該存檔的時間。所以我們只要把判定的數字寫小一點,系統便會經常性的進行儲存變數的動作。例子如下: SHIORI3FW.Svvercount++ if SHIORI3FW.Svvercount > 1800 { SHIORI3FW.Svvercount = 0 SAVEVAR } 改成 SHIORI3FW.Svvercount++ if SHIORI3FW.Svvercount > 60 { SHIORI3FW.Svvercount = 0 SAVEVAR } 這樣一來,存檔的動作就會從每1800秒一次,變成每60秒存一次檔*2,不過建議別把時間改得太短,免得系統負荷量太大。 測試程式的運行 (LV 2) †那麼,怎樣才能快速的測試程式的運作呢?首先請來到 \dic\aya_react.dic 辭書中,最底下應該會有個 OnKeyPress 函式,這是專門用來感應按鍵反應的事件函式,只要在這裡設好熱鍵的話,以後只要按個按鍵, AYA 就會執行你想做的事了。設定方法如下: OnKeyPress { if reference0 == "f1" { //---- 開啟readme.txt "\![open,readme]" } elseif reference0 == "c" { //---- 開始溝通 "\![open,communicatebox]" } else { "\0\s[0]你按了%(reference0)。編碼是%(reference1)。目前尚未被設定為熱鍵。" } } 改成 OnKeyPress { if reference0 == "f1" { //---- 開啟readme.txt "\![open,readme]" } elseif reference0 == "f2" { //---- 程式測試 TestFunction } elseif reference0 == "f12" { //---- 人格重新載入 "\0重新讀取SHORI……\![reload,shiori]" } elseif reference0 == "c" { //---- 開始溝通 "\![open,communicatebox]" } else { "\0\s[0]你按了%(reference0)。編碼是%(reference1)。目前尚未被設定為熱鍵。" } } TestFunction { TestVar = 10 "程式執行完畢。TestVar = %(TestVar)" } 改好之後重新啟動人格。這樣一來,以後只要按下 F2 , TestFunction 這個函式就會被執行一次。順道一提的是,每一次改完程式碼後,都必須重新載入人格,新的程式碼才會被啟用,所以上述的原始碼中一併附上了快速重新啟動的熱 鍵設定,這樣一來,每一次改完程式碼後,按下 F12 再按 F2 ,即可立即得到程式的執行結果。想要一併觀察變數的變化狀況的話,可以參考 觀察變數的變化 (LV 1) ,或者是在TestFunction裡面追加 SAVEVAR 函式來儲存變數。 不過,如果你新增的程式有語法錯誤的話,你馬上就會發現人格一動也不動了……(XD) Debug (LV 2) †寫程式是一種相常嚴謹的工作,所有的寫法都必須符合編譯器的規則,程式才能被編譯並執行。 AYA 在某些地方上也算是一種編譯器,你不照著她的規則來,她就會跟你鬧脾氣。所以,為了讓她的心情好一點,你只能乖乖的逐字檢查辭書中是否有不合她意的東西,然後儘快訂正。 當然, AYA 也不是那麼的壞心,她會告訴你到底是哪邊出了錯。只要你打開 ayame.log 這個檔案,裡面就會寫著你犯了什麼樣的錯誤,以及這個錯誤出現在哪個辭書的哪一行,為了讓你接下來的日子過得好一點,別考慮了,快去 Debug 它吧。 然而,有的時候,你會發現就算你的語法完全正確,也能通過SHIORI的編譯,但執行的結果卻不是你所要的東西,這樣的時候該怎麼辦呢?由於 AYA 也不是 VC++ 那種大程式,自然不可能幫你做逐行運算、插入中斷點之類的工作,所以我們只能土法煉鋼,在每段程式中間插入一個變數的指派動作,然後藉由觀察變數的值來判 斷程式是跑到哪裡後才出的錯。例子如下: TestFunction { TestVar = 0 // 程式A TestVar = 1 // 程式B TestVar = 2 // 程式C TestVar = 3 } 執行TestFunction後,如果 TestVar 最終的變數停留在 2 的情況下的話,那麼就可以肯定是程式C出了問題,接下來重複執行這樣的動作,直到最終確認了Bug的所在,然後訂正它,一切搞定! 輸出字串時的觀念 (LV3) †func1 { "你" + "好"; } func2 { _temp = "你" + "好"; _temp; } func3 { "你"; -- "好"; } 上述三個程式的結果都一樣是"你好",但實際在寫程式時,這邊比較建議的是第二種寫法,將全部要輸出的內容整合在一個變數內,再輸出其內容。當你的 函式中有不少敘述式、子階層與迴圈時,這種寫法可以減少BUG的發生。同時,也是為了將字串處理與程式執行分開,避免原始碼的混亂與難以閱讀。 第一種的寫法,通常用於OnAiTalk,這樣便利於書寫大量的對話而不會浪費太多的空間。 第三種通常用於比較複雜但又有一定規則的對話格式,例如選單對話。 機能類 †簡易偽春菜鬧鐘 (LV1) †這可以說是所有事件中最容易被實作的功能之一,大概也是實用性最高的功能了吧。 要實現這個功能我們需要對二個部份進行修改: 1. 使用者輸入鬧鐘時間 為了讓使用者可以呼叫這個功能,我們首先打開aya_menu.dic,在 OpenMenu 函式中追加一個選項: \q[設定鬧鐘,OnSetAlarm]\n 然後隨便找個地方追加以下內容: OnSetAlarm { "\0\s[0]要在什麼時候叫您呢?"; -- "\![open,timeinput,OnSetAlarmComplete]"; } OnSetAlarmComplete { AlarmTime = SPLIT(reference0,","); "\0\s[0]在%(AlarmTime[0])點%(AlarmTime[1])分的時候叫您嗎?\w8\n任務接受!"; } 這樣第一階段完成。另外,對話部份隨便你改w 2. 鬧鐘事件 這個部份要使用到 OnMinuteChange 這個事件,它會在分鐘數值改變(也就是xx分00秒)的時候執行一次,可作為鬧鐘的判定依據。不過要注意的是 OnMinuteChange 本身已經具有其他的功能(如整點報時功能),因此在設置的時候要注意各功能間的相容性。以紺野芽芽美為例,由於我們認為鬧鈴功能比報時功能重要,所以我們 優先判定鬧鈴的執行。這部份的程式碼片段於aya_aitalk.dic中,如下: //---- OnMinuteChange事件 --------------------------------------------------------------- OnMinuteChange { //---- 時間的分鐘為 0 時產生報時對話。其他情況時則執行重疊判定 if minute == 0 { //---- 分 = 0 的時候產生報時對話 "\0\s[0]現在時間是%(hour12)點。\w9" 我們改成 //---- OnMinuteChange事件 --------------------------------------------------------------- OnMinuteChange { //---- 判定是否到了鬧鈴設定的時間 if AlarmTime[0] == hour && AlarmTime[1] == minute { "\0\s[0]鬧鈴的時間到了~~\![sound,play,alarm.mp3]\x\![sound,stop]"; } //---- 時間的分鐘為 0 時產生報時對話。其他情況時則執行重疊判定 elseif minute == 0 { //---- 分 = 0 的時候產生報時對話 "\0\s[0]現在時間是%(hour12)點。\w9" 這樣一來當到達使用者所設定的時間時,偽春菜就會自動播放 alarm.mp3 這個檔案。至於要播的音樂檔請自己找一個檔把它的檔名改成alarm.mp3丟到人格的資料夾裡面,或是改掉上面的檔案路徑。\x是一個Sakura語 法,執行至此時會停下來等待使用者點擊對話框,對話繼續,然後音樂就會停止。是不是很像真的鬧鐘呢?XD 當然,上面的程式碼只是最基本的設置,並沒有考慮到如何設置多個鬧鈴時間,而且也沒有停用/啟用鬧鐘的選項,這邊只講述了必要的幾個關鍵。其餘的功能可以的話請各位把它當作自己的習題,試著做做看,你會發覺這樣的學習方式是件很有趣的事喔。 (^_^) 更新中...歡迎協助編輯或發表疑問 †討論區 †
|